﻿#include "seqbase/sequence.h"
#include <map>
#include <algorithm>

using namespace std;
using namespace anip;

class Sequence::Renderer : public GraphicsRenderer
{
private:
	Sequence& _seq;
	map<Element*, GraphicsRenderer*> _renderers;
	GraphicsRenderer& getRenderer(Element& fg);

public:
	Renderer(Sequence& seq) :
		_seq(seq) {}
	~Renderer();

	GraphicObject& graphicObject() const override { return _seq; }
	void activate() override;
	void deactivate() override;
	void setFrame(float n) override;
	void setPresentationFrame(double realTime) override;
	void render(GraphicsRenderContext& cxt) override;
};

GraphicsRenderer& Sequence::Renderer::getRenderer(Element& e)
{
	if (_renderers.find(&e) == _renderers.end())
	{
		GraphicsRenderer* r = e.frameGroup.createRenderer();
		_renderers.insert({ &e, r });
		return *r;
	}
	return *_renderers[&e];
}

Sequence::Renderer::~Renderer()
{
	for (auto p : _renderers)
		delete p.second;
}

void Sequence::Renderer::activate()
{
	GraphicsRenderer::activate();
	setFrame(_currentFrame);
}

void Sequence::Renderer::deactivate()
{
	GraphicsRenderer::deactivate();
	for (auto p : _renderers)
		p.second->deactivate();
}

void Sequence::Renderer::setFrame(float n)
{
	_active = true;
	_currentFrame = n;

	for (auto& e : _seq._elements)
	{
		auto& r = getRenderer(e);

		float dt = n - e._position;
		if (dt >= 0 && dt < e._duration)
		{
			if (!r.isActive()) r.activate();

			float tabs = e._cutStart + dt;
			float len = (float)e.frameGroup.length();
			// ANIP_FIXME: these tiling operations are seriously flawed;
			// they need future revisions!
			switch (e._temporalTile)
			{
			case TemporalTiling::None:
				break;
			case TemporalTiling::Clamp:
				tabs = clamp(tabs, 0.0f, len); break;
			case TemporalTiling::Repeat:
				tabs = fmodf(tabs, len); break;
			case TemporalTiling::Extrapolate:
				// ANIP_UNDONE
				break;
			case TemporalTiling::Mirror:
				float t2 = fmodf(tabs, 2.0f * len);
				tabs = t2 > len ? 2.0f * len - t2 : t2;
			}
			r.setFrame(tabs);
		}
		else if (r.isActive()) r.deactivate();
	}
}

void Sequence::Renderer::setPresentationFrame(double realTime)
{
	for (auto p : _renderers)
		if (p.second->isActive())
			p.second->setPresentationFrame(realTime);
}

void Sequence::Renderer::render(GraphicsRenderContext& cxt)
{
	GraphicsRenderContext v2 = cxt;
	/*v2.offset = v2.offset + position[_time];
	v2.zoom *= zoom[_time];*/
	for (auto p : _renderers)
		if (p.second->isActive()) p.second->render(cxt);
}

void Sequence::_applyChanges(Element& e)
{
	_duration = 0;
	for (auto& i : _elements)
		_duration = max(i._position + i._duration, _duration);
}

void Sequence::add(Element e)
{
	assert(e._parent == nullptr);
	e._parent = this;
	_elements.push_back(e);
	_applyChanges(e);
}

void anip::Sequence::add(initializer_list<Element> groups, int offset)
{
	for (auto i : groups)
	{
		i._position += offset;
		add(i);
	}
}

GraphicsRenderer* anip::Sequence::createRenderer()
{
	return new Renderer(*this);
}

serializer_implementation(Sequence)(Sequence& obj, ostream& out)
{
	binaryWrite<float>(obj._frameRate, out);
	binaryWrite<size_t>(obj._elements.size(), out);
	for (auto& e : obj._elements)
	{
		binaryWrite<int>(e._position, out);
		binaryWrite<int>(e._row, out);
		binaryWrite<int>(e._cutStart, out);
		binaryWrite<int>(e._duration, out);
		binaryWrite(e._temporalTile, out);
		anip::serialize<FrameGroup>(e.frameGroup, out);
	}
}

// ANIP_FIXME: I don't know how to handle the problem of the ownerships
// of newly deserialized FrameGroup's since they're all dynamically 
// allocated... Currently, nothing is done to ensure correct disposals.
deserializer_implementation(Sequence)(istream& in)
{
	float frameRate;
	binaryRead(frameRate, in);
	auto seq = new Sequence(frameRate);
	size_t nElements;
	binaryRead(nElements, in);
	for (; nElements > 0; nElements--)
	{
		int position, row, start, duration;
		TemporalTiling tile;
		binaryRead(position, in);
		binaryRead(row, in);
		binaryRead(start, in);
		binaryRead(duration, in);
		binaryRead(tile, in);
		FrameGroup* fg = deserialize<FrameGroup>(in);
		seq->add(Sequence::Element(*fg, position, duration, row, start, tile));
	}
	return seq;
}